﻿using System;
using System.Collections.Generic;
using System.Web.UI;
using BoldBrick.CSOB.Intranet.Utils;
using BoldBrick.OneIntranet.HR.DataObjects.Lists;
using BoldBrick.OneIntranet.HR.DataObjects.Lists.WorkflowHistoryListData;
using BoldBrick.OneIntranet.HR.Resources;
using BoldBrick.OneIntranet.HR.Workflows;
using BoldBrick.SharePoint.CommonControls.Extensions;
using Microsoft.SharePoint;
using System.Linq;
using Microsoft.SharePoint.Workflow;
using SiteColumns = BoldBrick.SharePoint.CommonControls.DataObjects.SiteColumns;

namespace BoldBrick.OneIntranet.HR.WebParts.HRWorkflowStatisticsWebPart
{
	public partial class HRWorkflowStatisticsWebPartUserControl : UserControl
	{
		#region ------ Properties -----------------------------------------------------------------

		/// <summary>
		/// Id of workflow to show
		/// </summary>
		public int WorkflowID { get; set; }

		/// <summary>
		/// Status of shown workflow
		/// </summary>
		public WorkflowStatus WorkflowStatus { get; set; }

		#endregion

		#region ------ Load Data ------------------------------------------------------------------

		/// <summary>
		/// Data for chart
		/// </summary>
		private List<HRStatisticsItem> Data
		{
			get
			{
				string dataKey = string.Format("HRWorkflowStatisticsWebPartUserControl.Data.{0}.{1}", this.WorkflowID, this.WorkflowStatus);
				List<HRStatisticsItem> items = CacheHelper.Get<List<HRStatisticsItem>>(dataKey);
				if (items == null)
				{
					items = GetData();
					CacheHelper.TrySet(dataKey, items);
				}
				return items;
			}
		}

		/// <summary>
		/// Gets data for charts
		/// </summary>
		/// <returns>Returns data for charts</returns>
		private List<HRStatisticsItem> GetData()
		{
			uint language = SPContext.Current.Web.Language;

			// get "Workflows" list
			SPList listWorkflow = SPContext.Current.Web.GetList(Lists.WorkflowsList);
			if (listWorkflow == null)
			{
				string errorMessage = ResourceBuilder.GetResource("HRWorkflowStatisticsWebPartUserControl_Error_WorkflowsListNotFound", language, Lists.WorkflowsList.GetListTitle(language));
				ShowError(errorMessage, true);
				return null;
			}

			// get selected workflow item
			SPListItem itemWorkflow = listWorkflow.TryGetItemById(this.WorkflowID);
			if (itemWorkflow == null)
			{
				string errorMessage = ResourceBuilder.GetResource("HRWorkflowStatisticsWebPartUserControl_Error_WorkflowNotFound", language);
				ShowError(errorMessage, true);
				return null;
			}

			// get "Workflow history" list
			SPList listWorkflowHistory = SPContext.Current.Web.GetList(Lists.WorkflowHistoryList);
			if (listWorkflowHistory == null)
			{
				string errorMessage = ResourceBuilder.GetResource("HRWorkflowStatisticsWebPartUserControl_Error_WorkflowHistoryListNotFound", language, Lists.WorkflowHistoryList.GetListTitle(language));
				ShowError(errorMessage, true);
				return null;
			}

			List<HRStatisticsItem> workflowSteps = GetWorkflowSteps(itemWorkflow);

			List<HRWorkflowHistoryItem> historyItems = GetWorkflowHistoryItems(listWorkflowHistory, itemWorkflow);

			List<HRWorkflowHistoryItem> historyItemsFiltered = GetStatisticsFiltered(historyItems);

			foreach (HRStatisticsItem statisticsItem in workflowSteps)
			{
				statisticsItem.CandidateCount = historyItemsFiltered.Count(item => item.Data.WorkflowStepId == statisticsItem.WorkflowStepID);
			}

			return workflowSteps;
		}

		/// <summary>
		/// Filters statistics according to web part settings
		/// </summary>
		/// <param name="historyItems">History items</param>
		/// <returns>Returns filtered statistics according to web part settings</returns>
		private List<HRWorkflowHistoryItem> GetStatisticsFiltered(List<HRWorkflowHistoryItem> historyItems)
		{
			IEnumerable<IGrouping<Guid, HRWorkflowHistoryItem>> groups = historyItems
				// group by workflow instances
				.GroupBy(item => item.WorkflowHistoryParentInstanceID);

			// add where clause by
			switch (this.WorkflowStatus)
			{
				case WorkflowStatus.InProgress:
					// select NOT completed workflow instances only
					groups = groups
						.Where(group => !group.Any(item => item.EventType == SPWorkflowHistoryEventType.WorkflowCompleted || item.EventType == SPWorkflowHistoryEventType.WorkflowCancelled));
					break;
				case WorkflowStatus.Finished:
					// select completed workflow instances only
					groups = groups
						.Where(group => group.Any(item => item.EventType == SPWorkflowHistoryEventType.WorkflowCompleted || item.EventType == SPWorkflowHistoryEventType.WorkflowCancelled));
					break;
				default:
					throw new ArgumentOutOfRangeException();
			}


			List<HRWorkflowHistoryItem> items = groups
				// order group descending by max date in each group
				.OrderByDescending(group => group.Max(item => item.DateOccured))
				// group by candidate ID
				.GroupBy(group => group.FirstOrDefault().CandidateId)
				// from each canidate group select the last workflow of canidate only
				.Select(group => group.FirstOrDefault())
				// select all hisotry items again
				.SelectMany(group => group)
				// select TaskCompleted only
				.Where(item => item.EventType == SPWorkflowHistoryEventType.TaskCompleted)
				.ToList();

			
			return items;
		}

		/// <summary>
		/// Gets list of <see cref="HRStatisticsItem"/> within <paramref name="itemWorkflow"/> workflow
		/// </summary>
		/// <param name="itemWorkflow">Workflow item</param>
		/// <returns>Returns list of <see cref="HRStatisticsItem"/> within <paramref name="itemWorkflow"/> workflow</returns>
		private static List<HRStatisticsItem> GetWorkflowSteps(SPListItem itemWorkflow)
		{
			SPListItemCollection itemSteps = HRWorkflowHelper.GetWorkflowStepItems(SPContext.Current.Web, itemWorkflow);
			
			List<HRStatisticsItem> statisticsItems = itemSteps.OfType<SPListItem>()
				.Select((item, index) => new HRStatisticsItem(item, index))
				.ToList();
			
			return statisticsItems;
		}

		/// <summary>
		/// Gets list of <see cref="HRWorkflowHistoryItem"/> for <paramref name="itemWorkflow"/> workflow
		/// </summary>
		/// <param name="listWorkflowHistory">Workflow history list</param>
		/// <param name="itemWorkflow">Workflow item</param>
		/// <returns>Returns list of <see cref="HRWorkflowHistoryItem"/> for <paramref name="itemWorkflow"/> workflow</returns>
		private static List<HRWorkflowHistoryItem> GetWorkflowHistoryItems(SPList listWorkflowHistory, SPListItem itemWorkflow)
		{
			SPQuery query = new SPQuery
			                	{
			                		Query = GetQueryHistory(itemWorkflow)
			                	};

			SPListItemCollection itemsWorkflowHistory = listWorkflowHistory.GetItems(query);
			List<HRWorkflowHistoryItem> items = itemsWorkflowHistory.OfType<SPListItem>()
				.Select(item => new HRWorkflowHistoryItem(item))
				.ToList();

			return items;
		}

		private static string GetQueryHistory(SPListItem itemWorkflow)
		{
			string dataValue = WorkflowHistoryListDataValue.GetParameter(WorkflowHistoryListDataValue.WorkflowItemIdKey, itemWorkflow.ID);

			string query =
				string.Format(
					@"
				<Where>
					<Or>
						<Eq>
							<FieldRef Name=""{2}"" />
							<Value Type=""WorkflowEventType"">{5}</Value>
						</Eq>
						<And>
							<Contains>
								<FieldRef Name=""{0}"" />
								<Value Type=""Note"">{1}</Value>
							</Contains>
							<Or>
								<Eq>
									<FieldRef Name=""{2}"" />
									<Value Type=""WorkflowEventType"">{3}</Value>
								</Eq>
								<Eq>
									<FieldRef Name=""{2}"" />
									<Value Type=""WorkflowEventType"">{4}</Value>
								</Eq>
							</Or>
						</And>
					</Or>
				</Where>",
					SiteColumns.DataColumn.InternalName, dataValue, 
					SiteColumns.EventTypeColumn.InternalName,
					(int)SPWorkflowHistoryEventType.WorkflowCompleted,
					(int)SPWorkflowHistoryEventType.TaskCompleted,
					(int)SPWorkflowHistoryEventType.WorkflowCancelled

					);
			return query;
		}

		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

			HideError();

			chart.DataSource = Data;
			chart.DataBind();
			
		}

		#endregion

		#region ------ Errors ---------------------------------------------------------------------

		private void ShowError(string message, bool hideChart)
		{
			this.chart.Visible = !hideChart;
			this.divError.InnerText = message;
			this.divError.Visible = true;
		}

		private void HideError()
		{
			this.chart.Visible = true;
			this.divError.Visible = false;
		}

		#endregion
		
	}
}
